package org.digitarts.rtms.spring.collectors.shell.commands;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.digitarts.rtms.helpers.StringHelper;
import org.digitarts.rtms.helpers.XMLHelper;
import org.digitarts.rtms.tracing.ITracer;
import org.digitarts.rtms.tracing.Trace;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class CommandResultExtract
{
	protected int							paragraphId			= 0;
	protected String						paragraphName		= null;
	protected String						entryName			= null;
	protected int[]							values				= null;
	protected String						tracingType			= ITracer.METRIC_TYPE_COUNTER_LONG;
	protected String						filterLines			= null;
	protected int							headerOffSet		= 0;
	protected Pattern						filterPattern		= null;
	protected Map<String, String>			tracerTypes			= new HashMap<String, String>();
	protected String						lineSplit			= "\n";
	protected Pattern						lineSplitPattern	= null;
	protected Map<String, String>			columnNameMappings	= new HashMap<String, String>();
	protected boolean						header				= true;
	protected Set<String>					removes				= new HashSet<String>();
	protected boolean						removesDefined		= false;
	protected Map<Integer, CellParser>		cellParsers			= new HashMap<Integer, CellParser>();
	protected Map<Integer, String>			aggregates			= new HashMap<Integer, String>();

	public static final String				AGGREGATE_AVERAGE	= "Average";
	public static final String				AGGREGATE_TOTAL		= "Total";
	protected static Map<String, String>	aggregateTypes		= new HashMap<String, String>();

	// =======
	// Initialize the aggregate types.
	// =======
	static
	{
		aggregateTypes.put(AGGREGATE_AVERAGE.toUpperCase(), AGGREGATE_AVERAGE);
		aggregateTypes.put(AGGREGATE_TOTAL.toUpperCase(), AGGREGATE_TOTAL);
	}

	public CommandResultExtract()
	{

	}

	public CommandResultExtract(String xml)
	{
		this(XMLHelper.parseXML(xml).getDocumentElement());
	}

	public CommandResultExtract(Element xml)
	{
		try
		{
			Node paragraphNode = XMLHelper.getChildNodeByName(xml, "paragraph", false);
			paragraphId = XMLHelper.getAttributeIntValueByName(paragraphNode, "id");
			paragraphName = XMLHelper.getAttributeValueByName(paragraphNode, "name");
			String tmp = XMLHelper.getAttributeValueByName(paragraphNode, "header");
			if (tmp != null)
			{
				header = "true".equalsIgnoreCase(tmp);
			}
			if (paragraphName == null)
				throw new Exception("Paragraph Name Cannot Be Null");
			Node columnsNode = XMLHelper.getChildNodeByName(xml, "columns", false);
			entryName = XMLHelper.getAttributeValueByName(columnsNode, "entryName");
			headerOffSet = XMLHelper.getAttributeIntValueByName(columnsNode, "offset");
			tmp = XMLHelper.getAttributeValueByName(columnsNode, "values");
			values = StringHelper.compileRange(tmp);
			List<Node> removeNodes = XMLHelper.getChildNodesByName(columnsNode, "remove", false);

			for (Node rNode : removeNodes)
			{
				tmp = XMLHelper.getNodeTextValue(rNode);
				if (tmp != null && tmp.length() > 1)
				{
					removes.add(tmp);
				}
			}
			removesDefined = (removes.size() > 0);
			List<Node> nameMappingNodes = XMLHelper.getChildNodesByName(columnsNode, "namemapping", false);
			for (Node mapNode : nameMappingNodes)
			{
				String to = XMLHelper.getAttributeValueByName(mapNode, "to");
				String from = XMLHelper.getAttributeValueByName(mapNode, "from");
				if (to != null && from != null)
				{
					columnNameMappings.put(from.trim(), to.trim());
				}
			}
			// ===================
			// Cell Parsers
			// ===================
			// <cellparser column="5" group="1">([\\d+]+)%</cellparser>
			// cellParsers
			List<Node> cellParserNodes = XMLHelper.getChildNodesByName(columnsNode, "cellparser", false);
			for (Node cellParserNode : cellParserNodes)
			{
				int column = XMLHelper.getAttributeIntValueByName(cellParserNode, "column");
				cellParsers.put(column, new CellParser(column, XMLHelper.getNodeTextValue(cellParserNode).trim(), XMLHelper.getAttributeIntValueByName(cellParserNode, "group")));
			}

			// ===================
			// Aggregates
			// ===================
			// <aggregate type="average" column="1"/>
			//			
			List<Node> aggregateNodes = XMLHelper.getChildNodesByName(columnsNode, "aggregate", false);
			for (Node aggregateNode : aggregateNodes)
			{
				String type = XMLHelper.getAttributeValueByName(aggregateNode, "type");
				if (!aggregateTypes.containsKey(type.toUpperCase()))
				{
					continue;
				}
				int[] columns = StringHelper.compileRange(XMLHelper.getAttributeValueByName(aggregateNode, "column"));
				for (int i : columns)
				{
					aggregates.put(i, type.toUpperCase());
				}

			}

			Node filterLineNode = XMLHelper.getChildNodeByName(xml, "filterLine", false);
			if (filterLineNode != null)
			{
				filterLines = XMLHelper.getNodeTextValue(filterLineNode).trim();
				filterPattern = Pattern.compile(filterLines);
			}
			Node tracersNode = XMLHelper.getChildNodeByName(xml, "tracers", false);
			if (tracersNode != null)
			{
				tmp = XMLHelper.getAttributeValueByName(tracersNode, "default");
				if (tmp != null)
					tracingType = tmp;
				List<Node> traceOverrideNodes = XMLHelper.getChildNodesByName(tracersNode, "tracer", false);
				for (Node toNode : traceOverrideNodes)
				{
					tracerTypes.put(XMLHelper.getNodeTextValue(toNode).trim(), XMLHelper.getAttributeValueByName(toNode, "type"));
				}
			}
			Node lineSplitNode = XMLHelper.getChildNodeByName(xml, "lineSplit", false);
			if (lineSplitNode != null)
			{
				tmp = XMLHelper.getNodeTextValue(lineSplitNode).trim();
				if (tmp != null)
				{
					lineSplit = tmp;
				}
			}
			lineSplitPattern = Pattern.compile(lineSplit);
		}
		catch (Exception e)
		{
			throw new RuntimeException("Failed to create CommandResultExtract from XML", e);
		}
	}

	public static void main(String[] args)
	{
		String sampleXml = "<CommandResultExtract><paragraph id=\"3\" name=\"CPU Utilization\"/><columns entryName=\"2\" values=\"3-9,12,19,29-21\" offset=\"0\"><namemapping from=\"%idle\" to=\"Percent Idle CPU\"/></columns><tracers default=\"SLONG\"><tracer type=\"SINT\"> 	%idle </tracer></tracers><filterLine>.*</filterLine></CommandResultExtract>";
		Element xml = XMLHelper.parseXML(sampleXml).getDocumentElement();
		CommandResultExtract cre = new CommandResultExtract(xml);
		System.out.println(cre.toString());
	}

	public Set<Trace> processResultParagraph(String paragraph)
	{
		// paragraph.replaceAll("\n\n", "");
		// System.out.println("Paragraph:\n=========================\n" +
		// StringHelper.clean(paragraph) + "\n=================================");
		Set<Trace> traces = new HashSet<Trace>();
		Map<Integer, String[]> aggregateColumns = new HashMap<Integer, String[]>();
		Map<Integer, String> aggregateColumnNames = new HashMap<Integer, String>();
		Map<Integer, String> aggrTypes = new HashMap<Integer, String>();
		Map<Integer, String> aggregateTracerTypes = new HashMap<Integer, String>();

		String[] lines = lineSplitPattern.split(paragraph);
		String[] headerLine = null;
		if (header)
		{
			headerLine = lines[0].trim().split("\\s+");
			if (removesDefined)
				StringHelper.removeEntry(headerLine, removes);
		}
		String[][] data = null;
		if (header)
		{
			data = new String[lines.length - 1][];
		}
		else
		{
			data = new String[lines.length][];
		}
		Map<Integer, String> fullLines = new HashMap<Integer, String>();
		Matcher lineMatcher = null;
		int rows = 0;
		String eName = null;
		String colName = null;
		String measurement = null;
		String traceType = null;
		List<String> nameSpace = new ArrayList<String>();
		for (String line : lines)
		{
			if (header)
			{
				if (rows > 0)
				{
					data[rows - 1] = line.trim().split("\\s+");
					fullLines.put(rows - 1, line.trim());
				}
			}
			else
			{
				data[rows] = line.trim().split("\\s+");
				fullLines.put(rows, line.trim());
			}
			rows++;
		}
		rows = 0;
		// prep aggregateColumns
		for (int valueInt : values)
		{
			if (aggregates.containsKey(valueInt))
			{
				aggregateColumns.put(valueInt, new String[data.length]);
				aggrTypes.put(valueInt, aggregates.get(valueInt));
				if (header)
				{
					colName = headerLine[valueInt + headerOffSet];
					if (columnNameMappings.containsKey(colName))
					{
						colName = columnNameMappings.get(colName);
					}
					aggregateColumnNames.put(valueInt, colName);
					// aggregateTracerTypes
					if (colName != null)
					{
						if (tracerTypes.containsKey(colName))
						{
							aggregateTracerTypes.put(valueInt, tracerTypes.get(colName));
						}
						else
						{
							aggregateTracerTypes.put(valueInt, tracingType);
							traceType = tracingType;
						}
					}
				}
			}
		}

		// start loop per row of data

		for (String[] dataLine : data)
		{

			if (dataLine.length < values.length)
			{ // this is a problem, sometimes.
				continue;
			}
			if (this.filterPattern != null)
			{
				lineMatcher = filterPattern.matcher(fullLines.get(rows));
				if (!lineMatcher.matches())
				{
					rows++;
					continue;
				}
			}
			if (removesDefined)
				StringHelper.removeEntry(dataLine, removes);

			// Starts loop per column, as indexed in valueInt

			for (int valueInt : values)
			{
				boolean isAggregate = aggregateColumns.containsKey(valueInt);
				if (!isAggregate)
				{
					nameSpace.clear();
					if (entryName != null)
					{
						eName = StringHelper.buildFromRange(entryName, "-", dataLine);
						// if(!header && columnNameMappings.containsKey(eName)) {
						if (columnNameMappings.containsKey(eName))
						{
							eName = columnNameMappings.get(eName);
						}
					}
					if (header)
					{
						colName = headerLine[valueInt + headerOffSet];
						if (columnNameMappings.containsKey(colName))
						{
							colName = columnNameMappings.get(colName);
						}
					}
					try
					{
						if (cellParsers.containsKey(valueInt))
						{
							measurement = cellParsers.get(valueInt).parseCell(dataLine[valueInt]);
						}
						else
						{
							measurement = dataLine[valueInt];
						}
					}
					catch (Exception e)
					{
						continue;
					}
					if (measurement == null)
						continue;
					nameSpace.add(paragraphName);
					if (eName != null)
					{
						nameSpace.add(eName);
						if (!header)
						{
							if (tracerTypes.containsKey(eName))
							{
								traceType = tracerTypes.get(eName);
							}
							else
							{
								traceType = tracingType;
							}
						}
					}
					if (colName != null)
					{
						nameSpace.add(colName);
						if (tracerTypes.containsKey(colName))
						{
							traceType = tracerTypes.get(colName);
						}
						else
						{
							traceType = tracingType;
						}
					}
					Trace trace = new Trace(nameSpace.toArray(new String[nameSpace.size()]), traceType, measurement);
					traces.add(trace);
				}
				else
				{
					// prepare aggregate
					if (cellParsers.containsKey(valueInt))
					{
						measurement = cellParsers.get(valueInt).parseCell(dataLine[valueInt]);
					}
					else
					{
						measurement = dataLine[valueInt];
					}
					aggregateColumns.get(valueInt)[rows] = measurement;

				}
			}
			rows++;
		}
		// aggregateColumnNames
		// aggregateTracerTypes
		for (Entry<Integer, String[]> entry : aggregateColumns.entrySet())
		{
			nameSpace.clear();
			nameSpace.add(paragraphName);
			String aggrType = aggrTypes.get(entry.getKey());
			String aggrDisplayName = aggregateTypes.get(aggrType.toUpperCase()) + " ";
			nameSpace.add(aggrDisplayName + aggregateColumnNames.get(entry.getKey()));

			measurement = calculateAggregate(entry.getValue(), aggrType);
			Trace trace = new Trace(nameSpace.toArray(new String[nameSpace.size()]), traceType, measurement);
			traces.add(trace);
		}
		return traces;
	}

	public static String calculateAggregate(String[] cells, String aggregateType)
	{
		String result = null;
		if (!aggregateTypes.containsKey(aggregateType) || cells == null || cells.length < 1)
		{
			// log.warn("Aggregate type of [" + aggregateType + "] is invalid");
			return null;
		}
		double total = 0D;
		int count = cells.length;
		for (String s : cells)
		{
			total += Double.parseDouble(s);
		}
		if (aggregateType.equalsIgnoreCase(AGGREGATE_TOTAL))
		{
			result = "" + total;
		}
		else if (aggregateType.equalsIgnoreCase(AGGREGATE_AVERAGE))
		{
			if (total == 0 || count == 0)
			{
				result = "0";
			}
			else
			{
				result = "" + (total / count);
			}
		}
		return result;
	}

	public int getParagraphId()
	{
		return paragraphId;
	}

	public void setParagraphId(int paragraphId)
	{
		this.paragraphId = paragraphId;
	}

	public String getParagraphName()
	{
		return paragraphName;
	}

	public void setParagraphName(String paragraphName)
	{
		this.paragraphName = paragraphName;
	}

	public String getEntryName()
	{
		return entryName;
	}

	public void setEntryName(String entryName)
	{
		this.entryName = entryName;
	}

	public int[] getValues()
	{
		return values;
	}

	public void setValues(int[] values)
	{
		this.values = values;
	}

	public String getTracingType()
	{
		return tracingType;
	}

	public void setTracingType(String tracingType)
	{
		this.tracingType = tracingType;
	}

	public String getFilterLines()
	{
		return filterLines;
	}

	public void setFilterLines(String filterLines)
	{
		this.filterLines = filterLines;
	}

	public int getHeaderOffSet()
	{
		return headerOffSet;
	}

	public void setHeaderOffSet(int headerOffSet)
	{
		this.headerOffSet = headerOffSet;
	}

	public String toString()
	{
		final String TAB = "    \n\t";

		StringBuilder retValue = new StringBuilder();
		StringBuilder buff = new StringBuilder((values.length * 2) + 3);
		buff.append("{");
		for (int i : values)
		{
			buff.append(i).append(",");
		}
		buff.deleteCharAt(buff.length() - 1);
		buff.append("}");
		StringBuilder buff2 = new StringBuilder();
		buff2.setLength(0);
		for (Entry<String, String> entry : columnNameMappings.entrySet())
		{
			buff2.append("\n\t\t").append(entry.getKey()).append(":").append(entry.getValue());
		}
		retValue.append("CommandResultExtract ( \n\t").append("paragraphId=").append(this.paragraphId).append(TAB).append("paragraphName=").append(this.paragraphName).append(TAB).append("entryName=")
				.append(this.entryName).append(TAB).append("values=").append(buff).append(TAB).append("tracingType=").append(this.tracingType).append(TAB).append("headerOffSet=").append(
						this.headerOffSet).append(TAB).append("filterPattern=").append(this.filterPattern).append(TAB).append("tracerTypes=").append(this.tracerTypes).append(TAB).append(
						"nameMappings=").append(buff2).append(TAB).append("lineSplit=[").append(StringHelper.clean(this.lineSplitPattern.pattern())).append("]\n").append(" )");

		return retValue.toString();
	}

	public boolean isHeader()
	{
		return header;
	}

	public void setHeader(boolean header)
	{
		this.header = header;
	}

}
